/**
 * @brief TH09C 华普微电子 温湿度传感器驱动
 * @author 张勇 / 2020-05-13
 */

#include "share/unios.h"
#include "simi2c.h"

// TH09 读写地址
#define TH09_FIXED_ADDR     0x43
#define	TH09_WRITE_ADDRESS  ((TH09_FIXED_ADDR << 1) | 0)
#define	TH09_READ_ADDRESS   ((TH09_FIXED_ADDR << 1) | 1)

// TH09 寄存器定义
//typedef struct _th09_reg {
//    uint8_t addr; // 寄存器地址
//    uint8_t size; // 寄存器宽度(字节数)
//} th09_reg_t;
//const th09_reg_t TH09_REG_PART_ID    = {0x00, 2}; // Read, Identifies the part number in little endian
//const th09_reg_t TH09_REG_DIE_REV    = {0x02, 2}; // Read, Identifies the die revision version
//const th09_reg_t TH09_REG_UID        = {0x04, 8}; // Read, Unique identifier
//const th09_reg_t TH09_REG_SYS_CTRL   = {0x10, 1}; // Read/Write, System configuration
//const th09_reg_t TH09_REG_SYS_STAT   = {0x11, 1}; // Read, System status
//const th09_reg_t TH09_REG_SENS_RUN   = {0x21, 1}; // Read/Write, The run mode (single shot or continuous)
//const th09_reg_t TH09_REG_SENS_START = {0x22, 1}; // Write, Start measurement
//const th09_reg_t TH09_REG_SENS_STOP  = {0x23, 1}; // Write, Stop continuous measurement
//const th09_reg_t TH09_REG_SENS_STAT  = {0x24, 1}; // Read, Sensor status (idle or measuring
//const th09_reg_t TH09_REG_T_VAL      = {0x30, 3}; // Read, Temperature readout
//const th09_reg_t TH09_REG_H_VAL      = {0x33, 3}; // Read, Relative humidity readout

// 7654 3211
// Polynomial 0b 1000 1001 ~ x^7+x^3+x^0
// 0x 8 9
#define CRC7WIDTH 7 // 7 bits CRC has polynomial of 7th order (has 8 terms)
#define CRC7POLY 0x89 // The 8 coefficients of the polynomial
#define CRC7IVEC 0x7F // Initial vector has all 7 bits high
// Payload data
#define DATA7WIDTH 17
#define DATA7MASK ((1UL<<DATA7WIDTH)-1) // 0b 0 1111 1111 11111111
#define DATA7MSB (1UL<<(DATA7WIDTH-1)) // 0b 1 0000 0000 00000000

// Compute the CRC-7 of ‘val’ (should only have 17 bits) 
static uint32_t _th09_crc7(uint32_t val) {
    // Setup polynomial
    uint32_t pol= CRC7POLY;
    // Align polynomial with data
    pol = pol << (DATA7WIDTH-CRC7WIDTH-1);
    // Loop variable (indicates which bit to test, start with highest)
    uint32_t bit = DATA7MSB;
    // Make room for CRC value
    val = val << CRC7WIDTH;
    bit = bit << CRC7WIDTH;
    pol = pol <<CRC7WIDTH;
    // Insert initial vector
    val |= CRC7IVEC;
    
    // Apply division until all bits done
    while( bit & (DATA7MASK<<CRC7WIDTH) ) {
        if( bit & val ) val ^= pol;
        bit >>= 1;
        pol >>= 1;
    }
    return val;
}

static std_err_t _th09_reg_write(unidrv_simi2c_t *dev, uint8_t regaddr, void *buf, size_t size) {
    std_err_t err = STD_ERR_OK;
	unidrv_simi2c_start(dev);
    do {
        if(!unidrv_simi2c_tx(dev, TH09_WRITE_ADDRESS)) { err = STD_ERR_TIMEOUT; break; }
        if(!unidrv_simi2c_tx(dev, regaddr)) { err = STD_ERR_TIMEOUT; break; }
        for(size_t i=0; i < size; i++) {
            if(!unidrv_simi2c_tx(dev, ((uint8_t*)buf)[i])) { err = STD_ERR_TIMEOUT; break; }
        }
    } while(0);
	unidrv_simi2c_stop(dev);
	return err;
}

static std_err_t _th09_reg_read(unidrv_simi2c_t *dev, uint8_t regaddr, void *buf, size_t size) {
    std_err_t err = STD_ERR_OK;
	unidrv_simi2c_start(dev);
    do {
        // write regaddr to read
        if(!unidrv_simi2c_tx(dev, TH09_WRITE_ADDRESS)) { err = STD_ERR_TIMEOUT; break; }
        if(!unidrv_simi2c_tx(dev, regaddr)) { err = STD_ERR_TIMEOUT; break; }
        // restart to read
        unidrv_simi2c_start(dev);
        if(!unidrv_simi2c_tx(dev, TH09_READ_ADDRESS)) { err = STD_ERR_TIMEOUT; break; }
        for(size_t i=0; i < size; i++) {
            ((uint8_t*)buf)[i] = unidrv_simi2c_rx(dev, i < (size - 1));
        }
    } while(0);
	unidrv_simi2c_stop(dev);
	return err;
}

static int32_t _th09_check_value(uint8_t buf[3]) {
    // Extract T_VAL and H_VAL (little endian), assumes 32 bits wordsize
    uint32_t val = (buf[2]<<16) | (buf[1]<<8) | buf[0];
    // Extract (and print) the fields
    uint32_t data  = (val>>0 ) & 0xffff;
    uint32_t valid = (val>>16) & 0x1;
    uint32_t crc   = (val>>17) & 0x7f;
    uint32_t payl  = (val>>0 ) & 0x1ffff;
    if(!valid || (_th09_crc7(payl) != crc)) return INT32_MIN;
    return data;
}

std_err_t unidrv_simi2c_th09c_read(unidrv_simi2c_t *dev, int *temperature, int *humidity) {
    std_err_t err;
    int32_t val;
    
    // Start T and H (write 03 to register 22 in device 86)
    uint8_t wbuf[1]= { 0x03 };
    err = _th09_reg_write(dev, 0x22, wbuf, sizeof(wbuf)); if(err) return err;
    
    // Wait for measurements to complete
    #define CONVERSION_TIME_T_H_MS 150
    unios_ms_delay(CONVERSION_TIME_T_H_MS);
    
    // Read T and H (read 6 bytes starting from 0x30 in device 86)
    uint8_t rbuf[6];
    err = _th09_reg_read(dev, 0x30, rbuf, sizeof(rbuf)); if(err) return err;
    
    if(temperature) {
        val = _th09_check_value(rbuf); if(val == INT32_MIN) return STD_ERR_INVALID_CRC;
        *temperature = val * 100 / 64 - 27315; // val / 64 - 273.15 (in 0.01C)
    }
    
    if(humidity) {
        val = _th09_check_value(rbuf + 3); if(val == INT32_MIN) return STD_ERR_INVALID_CRC;
        *humidity = val * 100/512; // relative humidity (in 0.01%)
    }
    
	return STD_ERR_OK;
}
